#!/usr/bin/perl
use strict;
use FindBin;
use JSON;
use File::Basename;
use Getopt::Long;
use File::Glob qw(bsd_glob);
use File::Temp;
use File::Path;
use WinCmd;
use File::Copy;
use MIME::Base64;
use Cwd;

use ServerAdapter;
use DeployUtils;
use DeployLock;

sub usage {
    my $pname = $FindBin::Script;

    print("Usage: $pname [--envpath EnvPath] [--version VERSION]\n");
    print("              [--verbose 0|1] [--protocol http|https] [--cpifexists 0|1] [--pull 0|1]\n");
    print("              [--pdir approot|project|release|appdist|dbscript|mirror]\n");
    print("              --transdir SaveToFileDirectoryToBeDownlaoded");
    print("              <src> <dest>\n");
    print("\n");
    print("       --envpath: Env path in the data directory, example:ATM/ATMP/PRD/ATMP-1\n");
    print("       --version: version number of sub system\n");
    print("       --protocol: WinRM protocol, http|https.\n");
    print("       --transdir: File transition directory.\n");
    print("       --cpifexists: copy if local dir or file exists.\n");
    print("       --pull: pull from remote site to local site\n");

    exit(-1);
}

sub getCopyCmd {
    my ( $method, $serverPath, $transName, $remote, $isPack ) = @_;

    $remote    = DeployUtils->url_encode($remote);
    $transName = DeployUtils->url_encode($transName);

    #PutFile "http://localhost:8080/easydeploy/filetrans" "d:/tmp/lc.rar" "TEST.tar.HHHHXXXX"
    #GetRemoteFile "http://localhost:8080/easydeploy/filetrans?fileName=cowork.war.src.tar" "d:/tmp/upload/cowork.war.src.down.tar";
    my ( $psPath, $cmd );
    if ( $method eq 'pull' ) {
        $psPath = "$FindBin::Bin/../tools/windeploy/upload.ps1";
        $cmd    = sprintf( 'PutFile "%s" "%s" "%s"', $serverPath, $remote, $transName );
    }
    else {
        $psPath = "$FindBin::Bin/../tools/windeploy/download.ps1";
        $cmd    = sprintf( 'GetRemoteFile "%s?fileName=%s" "%s" %d', $serverPath, $transName, $remote, $isPack );
    }

    my $content;
    my $fh = IO::File->new("<$psPath");
    if ($fh) {
        my $size = -s $psPath;
        $fh->read( $content, $size );
        $fh->close();
        $content =~ s/\s+/ /g;
    }

    $cmd = "$content $cmd";
    $cmd =~ s/\\/\\\\/g;
    $cmd =~ s/\"/\\\"/g;
    $cmd =~ s/\&/\"\&amp;\"/g;

    $cmd = "PowerShell -Command $cmd";

    #print("debug:$cmd\n");
    return $cmd;
}

sub main {
    my ( $isHelp, $isVerbose, $envPath, $version );
    my ( $isPull, $isPush, $cpIfExists, $protocol, $serverPath, $pdir, $node );

    my $pname = $FindBin::Script;

    $isPull    = 0;
    $isVerbose = 0;
    $protocol  = 'https';
    $pdir      = 'appdist';

    GetOptions(
        'envpath=s{0,1}' => \$envPath,
        'version=s'      => \$version,
        'h|help'         => \$isHelp,
        'v|verbose=i'    => \$isVerbose,
        'pull=i'         => \$isPull,
        'cpifexists=i'   => \$cpIfExists,
        'pdir=s'         => \$pdir,
        'protocol=s'     => \$protocol,
        'transdir=s'     => \$serverPath,
        'node=s'         => \$node,
        '<>'             => \&pushItems
    );

    usage() if ( defined($isHelp) );

    my ( @items, @dirs );

    sub pushItems {
        my ($item) = @_;
        push( @items, $item );
    }

    my $deployUtils = DeployUtils->new();

    my $optionError = 0;
    if ( scalar(@items) < 2 ) {
        print("ERROR: Must define local path and remote path.\n");
        $optionError = 1;
    }

    for ( my $i = 0 ; $i < scalar(@items) ; $i++ ) {
        my $dir = $items[$i];
        if ( $dir =~ /\/\.\.\// or $dir =~ /^\.\.\// or $dir =~ /\/\.\.$/ ) {
            print("ERROR: Path can not has parent dir opertor:\"..\".\n");
            $optionError = 1;
        }
        else {
            $dir = $deployUtils->charsetConv( $items[$i], 'utf-8' );
            $items[$i] = $dir;
        }
    }

    my $deployEnv = $deployUtils->deployInit( $envPath, $version );
    $envPath = $deployEnv->{NAME_PATH};
    $version = $deployEnv->{VERSION};

    if ( not defined($envPath) or $envPath eq '' ) {
        $optionError = 1;
        print("ERROR: EnvPath not defined by option --envpath or Environment:NAME_PATH\n");
    }
    if ( not defined($version) or $version eq '' ) {
        $optionError = 1;
        print("ERROR: Version not defined by option --version or Environment:VERSION\n");
    }

    my $namePath = $deployEnv->{NAME_PATH};
    my $envName  = $deployEnv->{ENV_NAME};

    my $serverAdapter = ServerAdapter->new();

    my $verInfo   = $serverAdapter->getEnvVer( $deployEnv, $version );
    my $verStatus = $verInfo->{status};
    my $buildNo   = $verInfo->{buildNo};
    if ( $verInfo->{status} ne 'released' ) {
        print("ERROR: $namePath version:$version is not released to $envName.\n");
        return 3;
    }

    my $dirInfo = $deployUtils->getDataDirStruct($deployEnv);
    my $verPath = $dirInfo->{$pdir};

    if ( defined($pdir) ) {
        if ( not defined($verPath) ) {
            print("ERROR: $pdir not valid, not in appsync|mirror|appbuild|version.\n");
            $optionError = 1;
        }
    }

    my $nodeInfo = $deployUtils->getNodeInfo($node);
    if ( not $nodeInfo ) {
        $optionError = 1;
        print("ERROR: Execute node json not defined by environment AUTOEXEC_NODE or option --node\n");
    }

    if ( $optionError == 1 ) {
        usage();
    }

    my $quietOpt = '';
    if ( $isVerbose == 1 ) {
        $quietOpt = 'v';
    }

    my ( @srcs, $dest );
    $dest = pop(@dirs);

    my $direction = 'push';
    if ( $isPull == 1 ) {
        $direction = 'pull';
    }

    my $autoexecHome  = $ENV{AUTOEXEC_HOME};
    my $transPathRoot = Cwd::fast_abs_path("$autoexecHome/data/filetrans");

    my $envName = $deployEnv->{ENV_NAME};
    my $verRoot = $dirInfo->{appRoot};

    my $host = $nodeInfo->{host};
    my $port = $nodeInfo->{protocolPort};
    my $user = $nodeInfo->{username};
    my $pass = $nodeInfo->{password};

    #my $insId = $nodeInfo->{resourceId};
    #my $insName     = $nodeInfo->{nodeName};
    my $insUniqName = $nodeInfo->{nodeUniqName};

    if ( defined($pass) and $pass ne '' ) {
        $pass = $deployUtils->decryptPwd($pass);
    }

    $port = 22 if ( not defined($port) );

    my $lock      = DeployLock->new($deployEnv);
    my $appLockId = $lock->lockEnvApp($DeployLock::WRITE);

    END {
        local $?;
        if ( defined($lock) ) {
            $lock->unlockEnvApp($appLockId);
        }
    }

    my $verDiffPath = "$verPath.ins/$insUniqName";

    if ($isPull) {
        @dirs = @items;
    }
    else {
        foreach my $dir (@items) {
            my $aSrc         = "$verPath/$dir";
            my @aSrcExpanded = bsd_glob($aSrc);

            if ( scalar(@aSrcExpanded) == 0 ) {
                if ( defined($cpIfExists) ) {
                    print("WARN: The src $aSrc does not match any file!\n");
                    next;
                }
            }

            foreach my $d (@aSrcExpanded) {
                push( @dirs, $d );
            }

            my $hasDiff  = 0;
            my @diffDirs = bsd_glob("$verDiffPath/$dir");
            foreach my $diffDir (@diffDirs) {
                if ( -e $diffDir ) {
                    $hasDiff = 1;
                    last;
                }
            }
            if ( $hasDiff == 1 ) {
                push( @dirs, "$verDiffPath/$dir" );
            }
        }
    }

    my $hasError = 0;

    if ( $isPull == 1 ) {
        $dest = "$verPath/$dest";

        foreach my $dir (@dirs) {
            if ( $dir =~ /[\/\\]\.\.[\/\\]/ or $dir =~ /^\.\.[\/\\]/ or $dir =~ /[\/\\]\.\.$/ ) {
                print("ERROR: FileTrans name can not has '..':$dir.\n");
                exit(-1);
            }

            my $randStr;
            $randStr = $randStr . sprintf( "%x", rand 16 ) for 1 .. 16;
            my $fileName = basename($dir);
            if ( $dir =~ /\\([^\\]+)$/ ) {
                $fileName = $1;
            }

            my $transName = "$fileName.$randStr";
            my $transPath = "$transPathRoot/$transName";

            my $fh    = IO::File->new(">$transPath");
            my $tarFh = IO::File->new(">$transPath.tar");

            $deployUtils->sigHandler(
                'TERM', 'INT', 'HUP', 'ABRT',
                sub {
                    rmtree($transPath)       if ( -e $transPath );
                    rmtree("$transPath.tar") if ( -e "$transPath.tar" );
                    return -1;
                }
            );

            if ($fh) {
                $fh->close();
                $tarFh->close();

                my $cmd = getCopyCmd( 'pull', $serverPath, $transName, $dir );

                my $winCmd = WinCmd->new( $protocol, $host, $port, $user, $pass, $cmd, $isVerbose );
                my $result = $winCmd->exec();

                if ( $result eq 0 ) {
                    my $fSzie   = -s $transPath;
                    my $tarSize = -s "$transPath.tar";

                    if ( $tarSize eq 0 ) {
                        if ( -d $dest ) {
                            move( $transPath, "$dest/$fileName" );
                        }
                        else {
                            move( $transPath, $dest );
                        }
                    }
                    else {
                        $deployUtils->execmd("tar -C '$dest' -x${quietOpt}f $transPath.tar");
                        unlink("$transPath.tar");
                    }

                    print("FINE: Pull $host:$dir to $dest success.\n");
                }
                else {
                    $hasError = $hasError + 1;
                    print("ERROR: Pull $host:$dir to $dest failed.\n");
                }
            }
            else {
                $hasError = $hasError + 1;
                print("ERROR: Permission deny for directory:$transPath.\n");
                exit($hasError);
            }
        }
    }
    else {
        foreach my $dir (@dirs) {
            if ( $dir =~ /^\.\.\// or $dir =~ /\/\.\.$/ or $dir =~ /\/\.\.\// ) {
                $hasError = $hasError + 1;
                print("ERROR: FileTrans name can not has '..':$dir.\n");
                exit($hasError);
            }

            #$dir = "$verPath/$dir";

            my ( $isPack, $randStr, $fileName, $transName );
            $isPack  = 0;
            $randStr = $randStr . sprintf( "%x", rand 16 ) for 1 .. 16;

            $fileName  = basename($dir);
            $transName = "$fileName.$randStr";
            my $transPath = "$transPathRoot/$transName";

            if ( -f $dir ) {
                if ( symlink( $dir, $transPath ) eq 0 ) {
                    print("ERROR: Path:create link $transPath permission deny.\n");
                    $hasError = $hasError + 1;
                    exit($hasError);
                }
            }
            elsif ( -d $dir ) {
                $isPack    = 1;
                $transName = "$transName.tar";
                $transPath = "$transPath.tar";

                #print("DEBUG:cd '$dir' && tar -c${quietOpt}f '$transPath' *\n");
                my $tarRet = $deployUtils->execmd("cd '$dir' && tar -c${quietOpt}f '$transPath' *");
                if ( $tarRet ne 0 ) {
                    print("ERROR: Tar $dir to $transPath failed.\n");
                    exit(-1);
                }
            }

            if ( not -e $transPath ) {
                print("ERROR: $dir does not exists or permission deny.\n");
                $hasError = $hasError + 1;
                exit($hasError);
            }

            my $cmd    = getCopyCmd( 'push', $serverPath, $transName, $dest, $isPack );
            my $winCmd = WinCmd->new( $protocol, $host, $port, $user, $pass, $cmd, $isVerbose );
            my $result = $winCmd->exec();

            if ( $result eq 0 ) {
                print("FINE: Push $dir to $host:$dest success.\n");
            }
            else {
                $hasError = $hasError + 1;
                print("ERROR: Push $dir to $host:$dest failed.\n");
            }

            unlink($transPath);
        }
    }

    if ( $hasError != 0 ) {
        print( "ERROR: $pname --$direction " . join( ' ', @dirs ) . " failed.\n" );
    }

    return $hasError;
}

exit main();

